﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Diagnostics;
using System.IO;
using System.Windows.Media.Animation;
using System.Windows.Threading;
using System.Windows.Controls.Primitives;
using Microsoft.Win32;

using PetriNetSharp.Engine;
using PetriNetSharp.View;
using PetriNetSharp.Controller;
using PetriNetSharp.Converter;
using System.Reflection;


namespace PetriNetSharp
{
	/// <summary>
	/// Interaction logic for MainWindow.xaml
	/// </summary>
	public partial class MainWindow : Window
	{
		#region constants

		/// <summary>
		/// The final width the the side panel
		/// </summary>
		private const int SIDE_PANEL_WIDTH = 58;

		/// <summary>
		/// The deceleration ratio for side panel grow/shrink animation
		/// </summary>
		private const double SIDE_PANEL_DECELERATION_RATIO = 0.7;

		/// <summary>
		/// The Z-index for ArcViews
		/// </summary>
		private const int ARC_VIEW_Z_INDEX = 0;

		#endregion

		/// <summary>
		/// The IO system to save and load graphs
		/// </summary>
		private IOSystem ioSystem;

		/// <summary>
		/// The underlying petri net graph engine
		/// </summary>
		private PetriNetGraph graph;

		/// <summary>
		/// The factory for creating arcs
		/// </summary>
		private ArcFactory arcFactory;

		/// <summary>
		/// The collection of current arc controllers
		/// </summary>
		private ObservableCollection<ArcController> arcControllers;

		/// <summary>
		/// The collection of current place controllers
		/// </summary>
		private ObservableCollection<PlaceController> placeControllers;

		/// <summary>
		/// The collection of current transition controllers
		/// </summary>
		private ObservableCollection<TransitionController> transitionControllers;

		/// <summary>
		/// Duration for side panel widht animation,currently 1/2 second
		/// </summary>
		private Duration sidePanelWidthDuration = new Duration(new TimeSpan(0, 0, 0, 0, 500));

		/// <summary>
		/// The timer for the auto fire mechanism
		/// </summary>
		private DispatcherTimer autoFireTimer = new DispatcherTimer();

		/// <summary>
		/// Converter used for binding autoFireTimer and autoFireComboBox's together
		/// </summary>
		private SecondStringToTimeSpanValueConverter secondStringToTimeSpanConverter =
			new SecondStringToTimeSpanValueConverter();

		
		/// <summary>
		/// Create a new MainWindow
		/// </summary>
		public MainWindow()
		{
			InitializeComponent();
			ioSystem = new IOSystem();
			ioSystem.CurrentFileChanged += () =>
				{
					Title = "PetriNetSharp - " + 
						System.IO.Path.GetFileNameWithoutExtension(ioSystem.CurrentFile);
				};
			graph = new PetriNetGraph();
			graph.IsRunningChanged += IsRunningChangedListener;
			graph.Fired += CheckIfGraphCanFire;
			graph.Resetted += CheckIfGraphCanFire;
			arcFactory = new ArcFactory();
			arcControllers = new ObservableCollection<ArcController>();
			placeControllers = new ObservableCollection<PlaceController>();
			transitionControllers = new ObservableCollection<TransitionController>();

			AttachRadioButtonEvents();
			AttachArcFactoryEventListeners();
			IntializeAutoFire();

			GrowModifySidePanel(null);

			////Since VS2008 uses this for the vshost process I'll try to a
			////add this back in once major debugging is done to see if it works
			//////Check if a file was passed in automatically
			////string[] args = Environment.GetCommandLineArgs();
			////if (args.Length > 0)
			////    Load(args[0]);
		}

		#region constructor helpers

		/// <summary>
		/// Attach the event listeners for the arc factory
		/// </summary>
		private void AttachArcFactoryEventListeners()
		{
			arcFactory.StartingPetriNetObjectAdded += delegate()
			{
				graphCanvas.Children.Add(arcFactory.ArcLine);
				graphCanvas.MouseMove += MoveArcLineEndToPointer;

				Point endPoint = Mouse.GetPosition(this.graphCanvas);
				arcFactory.ArcLine.X2 = endPoint.X;
				arcFactory.ArcLine.Y2 = endPoint.Y;
			};

			arcFactory.ArcCancelled += delegate(string message)
			{
				if (!String.IsNullOrEmpty(message))
				{
					MessageBox.Show(message, "Arc Creation Error", 
						MessageBoxButton.OK, MessageBoxImage.Error);
				}

				graphCanvas.Children.Remove(arcFactory.ArcLine);
				if (arcFactory.ArcHasStart)
				{
					graphCanvas.MouseMove -= MoveArcLineEndToPointer;
				}
			};

			arcFactory.ArcFinished += delegate(Arc arc)
			{
				ArcController controller = new ArcController(arcFactory.ArcLine, arc);
				arcControllers.Add(controller);
				controller.Invalidated += DeleteArc;
				graph.AddArc(arc);
				Canvas.SetZIndex(arcFactory.ArcLine, ARC_VIEW_Z_INDEX);
				graphCanvas.MouseMove -= MoveArcLineEndToPointer;
			};
		}

		/// <summary>
		/// Initialize the properties for the auto firing functionality to work.
		/// </summary>
		private void IntializeAutoFire()
		{
			//Bind the autoFireTimer enabled to the autoFireToggleButton state
			Binding bind = new Binding();
			bind.Source = autoFireTimer;
			bind.Path = new PropertyPath("IsEnabled");
			bind.Mode = BindingMode.TwoWay;
			autoFireCheckBox.SetBinding(CheckBox.IsCheckedProperty, bind);

			autoFireSecondsComboBox.Items.Add(1);
			autoFireSecondsComboBox.Items.Add(2);
			autoFireSecondsComboBox.Items.Add(3);

			autoFireSecondsComboBox.SelectionChanged += (object sender, SelectionChangedEventArgs e) =>
			{
				int seconds = int.Parse(autoFireSecondsComboBox.SelectedItem.ToString());
				autoFireTimer.Interval = new TimeSpan(0, 0, seconds);
			};

			autoFireSecondsComboBox.SelectedIndex = 0;

			autoFireTimer.Tick += (object sender, EventArgs e) => { FireGraph(); };
		}

		#endregion

		#region Attach Delegate Methods

		/// <summary>
		/// Add the event listeners to the radio buttons
		/// </summary>
		private void AttachRadioButtonEvents()
		{
			//Attach the events for the cursor radio button
			cursorRadioButton.Checked += delegate(object sender, RoutedEventArgs e)
				{
					graphCanvas.Cursor = Cursors.Arrow;
				};
			//Attach the listeners for the place radio button
			placeRadioButton.Checked += delegate(object sender, RoutedEventArgs e)
				{
					AttachPlaceAddListeners();

					Assembly assembly = Assembly.GetExecutingAssembly();
					Stream stream = assembly.GetManifestResourceStream("PetriNetSharp.Cursors.PlaceCursor.cur");
					graphCanvas.Cursor = new Cursor(stream);
				};

			placeRadioButton.Unchecked += delegate(object sender, RoutedEventArgs e)
				{
					DetachPlaceAddListeners();
				};

			//Attach the listeners for the transition radio button
			transitionRadioButton.Checked += delegate(object sender, RoutedEventArgs e)
				{
					AttachTransitionAddListeners();

					Assembly assembly = Assembly.GetExecutingAssembly();
					Stream stream = assembly.GetManifestResourceStream("PetriNetSharp.Cursors.TransitionCursor.cur");
					graphCanvas.Cursor = new Cursor(stream);
				};

			transitionRadioButton.Unchecked += delegate(object sender, RoutedEventArgs e)
				{
					DetachTransitionAddListeners();
				};
			//Attach the listeners for the arc radio button
			arcRadioButton.Checked += delegate(object sender, RoutedEventArgs e)
				{
					arcFactory.StartNewArc();
					Assembly assembly = Assembly.GetExecutingAssembly();
					Stream stream = assembly.GetManifestResourceStream("PetriNetSharp.Cursors.ArcCursor.cur");
					graphCanvas.Cursor = new Cursor(stream);
				};
			arcRadioButton.Unchecked += delegate(object sender, RoutedEventArgs e)
				{
					arcFactory.StopArcCreation();
				};
		}


		/// <summary>
		/// Attach the listeners so that when the graph canvas is clicked 
		/// a place is added
		/// </summary>
		private void AttachPlaceAddListeners()
		{
			graphCanvas.MouseLeftButtonDown += AddPlaceToGraph;
		}

		/// <summary>
		/// Detach the listeners so that when the graph canvas is clicked
		/// a place is not added
		/// </summary>
		private void DetachPlaceAddListeners()
		{
			graphCanvas.MouseLeftButtonDown -= AddPlaceToGraph;
		}

		/// <summary>
		/// Attach the listeners so that when the graph canvas is clicked
		/// a transition is added
		/// </summary>
		private void AttachTransitionAddListeners()
		{
			graphCanvas.MouseLeftButtonDown += AddTransitionToGraph;
		}

		/// <summary>
		/// Detach the listeners so that when the graph canvas is clicked
		/// a transition is not added
		/// </summary>
		private void DetachTransitionAddListeners()
		{
			graphCanvas.MouseLeftButtonDown -= AddTransitionToGraph;
		}

		#endregion

		#region Graph Object Add Methods

		/// <summary>
		/// Set up the view and controller for a place that is already
		/// added to the logical graph
		/// </summary>
		/// <param name="place">The place to add</param>
		private void AddPlaceFromGraph(Place place)
		{
			PlaceView view = new PlaceView();
			PlaceController controller = new PlaceController(view, place);
			placeControllers.Add(controller);

			controller.Invalidated += DeletePlace;

			graphCanvas.Children.Add(view);
			Canvas.SetZIndex(view, 1);


			//Add the event handlers for moving the place
			view.MouseLeftButtonDown += delegate(object esender, MouseButtonEventArgs se)
			{
				controller.StartMove(se.GetPosition(graphCanvas));
				se.Handled = true;
			};
			view.MouseLeftButtonUp += delegate(object esender, MouseButtonEventArgs ee)
			{
				controller.EndMove();
			};
			view.MouseMove += delegate(object msender, MouseEventArgs me)
			{
				controller.MovePlace(me.GetPosition(this.graphCanvas));
			};

			//Add the event handlers for arc creation
			view.PreviewMouseLeftButtonDown += delegate(object esender, MouseButtonEventArgs se)
			{
				if (arcFactory.IsArcBeingCreated)
				{
					arcFactory.AddPetriObject(place);
					se.Handled = true;
				}
			};
			FocusManager.SetFocusedElement(graphCanvas, view);
		}

		/// <summary>
		/// Add a place to the graph
		/// </summary>
		/// <param name="sender">The original sender of the event</param>
		/// <param name="e">The arguments passed with this event</param>
		private void AddPlaceToGraph(object sender, MouseButtonEventArgs e)
		{
			AddPlaceFromGraph(graph.AddPlace(e.GetPosition(graphCanvas)));
		}

		/// <summary>
		/// Set up the view and controller for a transition that is already
		/// added to the logical graph
		/// </summary>
		/// <param name="transition">The transition to add</param>
		private void AddTransitionFromGraph(Transition transition)
		{
			TransitionView view = new TransitionView();
			TransitionController controller = new TransitionController(transition, view);
			transitionControllers.Add(controller);
			graphCanvas.Children.Add(view);

			controller.Invalidated += DeleteTransition;

			Canvas.SetZIndex(view, 1);

			//Add the event handlers for moving the transition
			view.MouseLeftButtonDown += delegate(object ssender, MouseButtonEventArgs se)
			{
				controller.StartMove(se.GetPosition(graphCanvas));
				se.Handled = true;
			};
			view.MouseLeftButtonUp += delegate(object esender, MouseButtonEventArgs ee)
			{
				controller.EndMove();
			};
			view.MouseMove += delegate(object msender, MouseEventArgs me)
			{
				controller.MovePlaceView(me.GetPosition(graphCanvas));
			};

			//Add the event handlers for arc creation
			view.PreviewMouseLeftButtonDown += delegate(object esender, MouseButtonEventArgs se)
			{
				if (arcFactory.IsArcBeingCreated)
				{
					arcFactory.AddPetriObject(transition);
					se.Handled = true;
				}
			};
			FocusManager.SetFocusedElement(graphCanvas, view);
		}

		/// <summary>
		/// Add a transition to the graph
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void AddTransitionToGraph(object sender, MouseButtonEventArgs e)
		{
			AddTransitionFromGraph(graph.AddTransition(e.GetPosition(graphCanvas)));
		}

		#endregion


		/// <summary>
		/// Move the current arcline end to the mouse pointer
		/// </summary>
		/// <param name="sender">Not used</param>
		/// <param name="e">Not used</param>
		private void MoveArcLineEndToPointer(object sender, MouseEventArgs e)
		{
			Point currentPoint = e.GetPosition(graphCanvas);
			arcFactory.ArcLine.X2 = currentPoint.X;
			arcFactory.ArcLine.Y2 = currentPoint.Y;
		}

		

		#region running/stopped functionality

		/// <summary>
		/// Start the Petri Net Graph
		/// </summary>
		private void StartGraph()
		{
			graph.Start();
		}

		/// <summary>
		/// Stop the Petri Net Graph
		/// </summary>
		private void StopGraph()
		{
			autoFireCheckBox.IsChecked = false;
			graph.Stop();
		}

		/// <summary>
		/// Listens for when the graphs IsRunning property changes
		/// </summary>
		private void IsRunningChangedListener()
		{
			if (graph.IsRunning)
				GraphStarted();
			else
				GraphStopped();
		}

		/// <summary>
		/// Changes the UI to reflect that the graph has started
		/// </summary>
		private void GraphStarted()
		{
			//Check the cursor radio button to clear any add listeners on the graph
			cursorRadioButton.IsChecked = true;

			//Shrink the modifyDockPanel and then grow the runningDockPanel
			ShrinkModifySidePanel(null);
			DoubleAnimation growRunningPanelAnimation = new DoubleAnimation();
			//Have the animation wait the time it takes to shrink the side panel
			growRunningPanelAnimation.BeginTime = sidePanelWidthDuration.TimeSpan;
			growRunningPanelAnimation.To = SIDE_PANEL_WIDTH;
			growRunningPanelAnimation.DecelerationRatio = SIDE_PANEL_DECELERATION_RATIO;
			GrowRunningSidePanel(growRunningPanelAnimation);

			//Make sure the UI is updated as to whether the current graph can fire
			CheckIfGraphCanFire();
		}

		/// <summary>
		/// Changes the UI to reflect that the graph has stopped
		/// </summary>
		private void GraphStopped()
		{
			//Shrink the runningDockPanel and then grow the modifyDockPanel
			ShrinkRunningSidePanel(null);
			DoubleAnimation growModifyPanelAnimation = new DoubleAnimation();
			//Have the animation wait the time it takes to shrink the side panel
			growModifyPanelAnimation.BeginTime = sidePanelWidthDuration.TimeSpan;
			growModifyPanelAnimation.To = SIDE_PANEL_WIDTH;
			growModifyPanelAnimation.DecelerationRatio = SIDE_PANEL_DECELERATION_RATIO;
			GrowModifySidePanel(growModifyPanelAnimation);


			graphCanvas.SetResourceReference(Canvas.BackgroundProperty, "ModifyGraphBackgroundBrush");
			HideAllArcFireEffects();
		}

		/// <summary>
		/// Fire the graph
		/// </summary>
		private void FireGraph()
		{
			HideAllArcFireEffects();
			graph.FireAllTransitions();
		}

		/// <summary>
		/// Checks if the graph can fire and updates the proper UIElements
		/// to refelct if it can
		/// </summary>
		private void CheckIfGraphCanFire()
		{
			if (graph.CanFire)
			{
				manualFireRadioButton.IsEnabled = true;
				autoFireCheckBox.IsEnabled = true;
				graphCanvas.SetResourceReference(Canvas.BackgroundProperty, "CanFireGraphBackgroundBrush");
			}
			else
			{
				manualFireRadioButton.IsEnabled = false;
				autoFireCheckBox.IsChecked = false;
				autoFireCheckBox.IsEnabled = false;
				graphCanvas.SetResourceReference(Canvas.BackgroundProperty, "CannotFireGraphBackgroundBrush");
			}
		}

		/// <summary>
		/// Hide all fire effects on all Arcs
		/// </summary>
		private void HideAllArcFireEffects()
		{
			foreach (ArcController arc in arcControllers)
				arc.HideFireHighlight();
		}

		/// <summary>
		/// Show all fire effects on all arcs
		/// </summary>
		private void ShowAllArcFireEffects()
		{
			foreach (ArcController arc in arcControllers)
				arc.ShowFireHighlight();
		}

		/// <summary>
		/// Reset the running graph to be how it was when it first started
		/// </summary>
		/// <preconditions>
		/// Graph must be running
		/// </preconditions>
		private void ResetGraph()
		{
			#region preconditions
			Debug.Assert(graph.IsRunning, "ResetGraph called when graph is not running",
				"ResetGraph() was called when graph.IsRunning was false");
			#endregion
			HideAllArcFireEffects();
			graph.Reset();
			autoFireCheckBox.IsChecked = false;
		}

		#endregion

		#region grow/shrink side panel functionality

		/// <summary>
		/// Grow the modify side panel
		/// </summary>
		/// <param name="animation">Then animation you would like it to use,
		/// use null to use the standard animations</param>
		private void GrowModifySidePanel(DoubleAnimation animation)
		{
			DoubleAnimation sidePanelWidthAnimation;
			if (animation == null)
			{
				sidePanelWidthAnimation = new DoubleAnimation();
				sidePanelWidthAnimation.To = SIDE_PANEL_WIDTH;
				sidePanelWidthAnimation.Duration = sidePanelWidthDuration;
				sidePanelWidthAnimation.DecelerationRatio = SIDE_PANEL_DECELERATION_RATIO;
			}
			else
				sidePanelWidthAnimation = animation;
			modifyDockPanel.BeginAnimation(DockPanel.WidthProperty, sidePanelWidthAnimation);
		}

		/// <summary>
		/// Shrink the modify side panel
		/// </summary>
		/// <param name="animation">Then animation you would like it to use,
		/// use null to use the standard animations</param>
		private void ShrinkModifySidePanel(DoubleAnimation animation)
		{
			DoubleAnimation sidePanelWidthAnimation;
			if (animation == null)
			{
				sidePanelWidthAnimation = new DoubleAnimation();
				sidePanelWidthAnimation.To = 0;
				sidePanelWidthAnimation.Duration = sidePanelWidthDuration;
				sidePanelWidthAnimation.DecelerationRatio = SIDE_PANEL_DECELERATION_RATIO;
			}
			else
				sidePanelWidthAnimation = animation;
			modifyDockPanel.BeginAnimation(DockPanel.WidthProperty, sidePanelWidthAnimation);
		}

		/// <summary>
		/// Grow the running side panel
		/// </summary>
		/// <param name="animation">Then animation you would like it to use,
		/// use null to use the standard animations</param>
		private void GrowRunningSidePanel(DoubleAnimation animation)
		{
			DoubleAnimation sidePanelWidthAnimation;
			if (animation == null)
			{
				sidePanelWidthAnimation = new DoubleAnimation();
				sidePanelWidthAnimation.To = SIDE_PANEL_WIDTH;
				sidePanelWidthAnimation.Duration = sidePanelWidthDuration;
				sidePanelWidthAnimation.DecelerationRatio = SIDE_PANEL_DECELERATION_RATIO;
			}
			else
				sidePanelWidthAnimation = animation;
			runningDockPanel.BeginAnimation(DockPanel.WidthProperty, sidePanelWidthAnimation);
		}

		/// <summary>
		/// Shrink the running side panel
		/// </summary>
		/// <param name="animation">Then animation you would like it to use,
		/// use null to use the standard animations</param>
		private void ShrinkRunningSidePanel(DoubleAnimation animation)
		{
			DoubleAnimation sidePanelWidthAnimation;
			if (animation == null)
			{
				sidePanelWidthAnimation = new DoubleAnimation();
				sidePanelWidthAnimation.To = 0;
				sidePanelWidthAnimation.Duration = sidePanelWidthDuration;
				sidePanelWidthAnimation.DecelerationRatio = SIDE_PANEL_DECELERATION_RATIO;
			}
			else
				sidePanelWidthAnimation = animation;
			runningDockPanel.BeginAnimation(DockPanel.WidthProperty, sidePanelWidthAnimation);
		}
		#endregion

		#region delete petri objects methods

		/// <summary>
		/// Deletes the passed TransitionController
		/// </summary>
		/// <param name="controller">The TransitionController to remove</param>
		/// <preconditions>
		/// controller must be of type TransitionController
		/// </preconditions>
		private void DeleteTransition(IControllerInvalidatable controller)
		{
			#region preconditions
			Debug.Assert(controller is TransitionController, 
				"DeleteTransition parameter not a TransitionController",
				"DeleteTransition was called, but not was not passed a TransitionController for its parameter");
			#endregion

			TransitionController tController = controller as TransitionController;

			//Delete from logical graph, view graph, and collection of controllers
			graph.DeleteTransition(tController.Model);
			graphCanvas.Children.Remove(tController.View);
			transitionControllers.Remove(tController);
		}

		/// <summary>
		/// Deletes the passed PlaceController
		/// </summary>
		/// <param name="controller">The PlaceController to remove</param>
		/// <preconditions>
		/// controller must be of type PlaceController
		/// </preconditions>
		private void DeletePlace(IControllerInvalidatable controller)
		{
			#region preconditions
			Debug.Assert(controller is PlaceController,
				"DeletePlace parameter not a PlaceController",
				"DeletePlace was called, but not was not passed a PlaceController for its parameter");
			#endregion

			PlaceController pController = controller as PlaceController;

			//Delete from logical graph, view graph, and collection of controllers
			graph.DeletePlace(pController.Model);
			graphCanvas.Children.Remove(pController.View);
			placeControllers.Remove(pController);
		}

		/// <summary>
		/// Deletes the passed ArcController
		/// </summary>
		/// <param name="controller">The ArcController to remove</param>
		/// <preconditions>
		/// controller must be of type ArcController
		/// </preconditions>
		private void DeleteArc(IControllerInvalidatable controller)
		{
			#region preconditions
			Debug.Assert(controller is ArcController,
				"DeleteArc parameter not a ArcController",
				"DeleteArc was called, but not was not passed a ArcController for its parameter");
			#endregion

			ArcController aController = controller as ArcController;
			
			//Delete from logical graph, view graph, and collection of controllers
			graph.DeleteArc(aController.Model);
			graphCanvas.Children.Remove(aController.View);
			arcControllers.Remove(aController);
		}

		#endregion

		#region save, load and export functionality

		/// <summary>
		/// Prints the graph
		/// </summary>
		private void PrintGraph()
		{
			PrintDialog printDialog = new PrintDialog();
			if (printDialog.ShowDialog() == true)
			{
				//Convert the graph into an image so that it will be exactly as its 
				//image conversion
				int height;
				int width;
				FindGraphSize(out height, out width);
				RenderTargetBitmap bitmap = new RenderTargetBitmap(width, height,
					96, 96, PixelFormats.Default);
				bitmap.Render(graphCanvas);
				PngBitmapEncoder png = new PngBitmapEncoder();
				png.Frames.Add(BitmapFrame.Create(bitmap));
				Image image = new Image();
				image.Source = png.Frames[0];
				printDialog.PrintVisual(image, "PetriNetSharp - " + ioSystem.CurrentFile);
			}
		}

		/// <summary>
		/// Exports the current graph as an Image
		/// </summary>
		private void ExportGraphAsImage()
		{
			string pngExtension = ".png";
			string jpgExtension = ".jpg";
			string gifExtenstion = ".gif";
			string file = ShowSaveFileDialog("*png", 
				"PNG Image|*" + pngExtension + 
				"|JPG Image|*" + jpgExtension + 
				"|GIF Image|*"+ gifExtenstion + 
				"|All Files|*.*");
			if (!String.IsNullOrEmpty(file))
			{
				int height;
				int width;
				FindGraphSize(out height, out width);
				RenderTargetBitmap bitmap = new RenderTargetBitmap(width, height, 
					96, 96, PixelFormats.Default);
				bitmap.Render(graphCanvas);

				BitmapEncoder image = null;

				//Set the the encoder
				string extension = System.IO.Path.GetExtension(file);
				if (extension.Equals(pngExtension))
					image = new PngBitmapEncoder();
				else if (extension.Equals(jpgExtension))
					image = new JpegBitmapEncoder();
				else if (extension.Equals(gifExtenstion))
					image = new GifBitmapEncoder();

				try
				{
					using (System.IO.Stream stm = System.IO.File.Create(file))
					{
						image.Frames.Add(BitmapFrame.Create(bitmap));
						image.Save(stm);
					}
				}
				catch (Exception e)
				{
					MessageBox.Show("Unable to export to that location, check access rights.",
						"Unable to export", MessageBoxButton.OK, MessageBoxImage.Error);
				}
			}
		}

		/// <summary>
		/// Find the current graph size, this is used for exported the graph as an image
		/// </summary>
		/// <param name="height">The Height of the graph</param>
		/// <param name="width">The Width of the graph</param>
		private void FindGraphSize(out int height, out int width)
		{
			int exportBuffer = 100; //The amount of added space to add to height and width
			ReadOnlyObservableCollection<Place> places = graph.Places;
			ReadOnlyObservableCollection<Transition> transitions = graph.Transitions;
			height = 0;
			width = 0;

			foreach (Place place in places)
			{
				if (place.Location.X > width)
					width = (int)place.Location.X;
				if (place.Location.Y > height)
					height = (int)place.Location.Y;
			}

			foreach (Transition trans in transitions)
			{
				if (trans.Location.X > width)
					width = (int)trans.Location.X;
				if (trans.Location.Y > height)
					height = (int)trans.Location.Y;
			}

			width += exportBuffer;
			height += exportBuffer;

		}

		/// <summary>
		/// Shows a Save File Dialog to the user
		/// </summary>
		/// <param name="defaultExtenstion">The default extension for the dialog</param>
		/// <param name="filter">
		/// The filter for the dialog
		/// <remarks>
		/// See SaveFileDialog.Filter for more information about format
		/// </remarks>
		/// </param>
		/// <returns>Returns the file path, return "" if cancelled</returns>
		private string ShowSaveFileDialog(string defaultExtenstion, string filter)
		{
			SaveFileDialog saveDialog = new SaveFileDialog();
			saveDialog.DefaultExt = defaultExtenstion;
			saveDialog.AddExtension = true;
			saveDialog.Filter = filter;
			saveDialog.FilterIndex = 0;

			if ((bool)saveDialog.ShowDialog())
				return saveDialog.FileName;
			else
				return "";
		}

		/// <summary>
		/// Shows a load file dialog to the user
		/// </summary>
		/// <returns>Returns the file path, returns "" if cancelled</returns>
		private string ShowLoadFileDialog()
		{
			OpenFileDialog loadDialog = new OpenFileDialog();
			loadDialog.DefaultExt = "*.pns";
			loadDialog.Filter = "PetriNetSharp Graphs|*.pns|All Files|*.*";
			loadDialog.FilterIndex = 0;

			if ((bool)loadDialog.ShowDialog())
				return loadDialog.FileName;
			else
				return "";
		}

		/// <summary>
		/// Brings up a save file dialog to the user and saves the current graph
		/// </summary>
		private void SaveAs()
		{
			string file = ShowSaveFileDialog(IOSystem.FILE_EXTENSTION, IOSystem.FILE_SAVE_FILTER);
			if (!string.IsNullOrEmpty(file))
				ioSystem.SaveAs(graph, file);
		}

		/// <summary>
		/// If the current graph is already saved somewhere it will automatically save
		/// over that, else it will act like SaveAs()
		/// </summary>
		private void Save()
		{
			if (ioSystem.HasAFile)
				ioSystem.Save(graph);
			else
			{
				string file = ShowSaveFileDialog(IOSystem.FILE_EXTENSTION, IOSystem.FILE_SAVE_FILTER);
				if (!string.IsNullOrEmpty(file))
					ioSystem.SaveAs(graph, file);
			}
		}

		/// <summary>
		/// Present a load dialog the user and then load the selected
		/// file
		/// </summary>
		private void Load()
		{
			string file = ShowLoadFileDialog();
			if(!string.IsNullOrEmpty(file))
				Load(file);
		}

		/// <summary>
		/// Load the file passed
		/// </summary>
		/// <param name="file">The path to the file</param>
		private void Load(string file)
		{
			try
			{
				//This is done with temp graph so that if the file does 
				//not load properly the event listeners are not removed
				//from the old graph as it would still be the current
				//graph
				PetriNetGraph tempGraph = ioSystem.Load(file);

				graph.IsRunningChanged -= IsRunningChangedListener;
				graph.Fired -= CheckIfGraphCanFire;
				graph.Resetted -= CheckIfGraphCanFire;

				graph = tempGraph;

				graph.IsRunningChanged += IsRunningChangedListener;
				graph.Fired += CheckIfGraphCanFire;
				graph.Resetted += CheckIfGraphCanFire;

				//Clear out the controllers and views
				arcControllers = new ObservableCollection<ArcController>();
				transitionControllers = new ObservableCollection<TransitionController>();
				placeControllers = new ObservableCollection<PlaceController>();
				graphCanvas.Children.Clear();

				//Add the places
				foreach (Place place in graph.Places)
					AddPlaceFromGraph(place);

				//Add the transitions
				foreach (Transition trans in graph.Transitions)
					AddTransitionFromGraph(trans);

				//Add the arcs
				foreach (Arc arc in graph.Arcs)
				{
					Line view = new Line();
					graphCanvas.Children.Add(view);
					ArcController controller = new ArcController(view, arc);
					arcControllers.Add(controller);
					controller.Invalidated += DeleteArc;
					Canvas.SetZIndex(view, ARC_VIEW_Z_INDEX);
				}
			}
			catch (Exception e)
			{
				MessageBox.Show("That file appears to be either corrupt or not a PetriNetSharp save file", 
					"File Load Problem", MessageBoxButton.OK, MessageBoxImage.Exclamation);
			}
		}

		#endregion

		#region running button listeners

		/// <summary>
		/// Starts the graph running
		/// </summary>
		/// <param name="sender">Not used</param>
		/// <param name="e">Not used</param>
		private void runButton_Click(object sender, RoutedEventArgs e)
		{
			StartGraph();
		}

		/// <summary>
		/// Stops the graphs from running and returns to modify mode
		/// </summary>
		/// <param name="sender">Not used</param>
		/// <param name="e">Not used</param>
		private void modifyButton_Click(object sender, RoutedEventArgs e)
		{
			StopGraph();
		}

		/// <summary>
		/// Fires the all transitions in the graph
		/// </summary>
		/// <param name="sender">Not used</param>
		/// <param name="e">Not used</param>
		private void manualFireRadioButton_Click(object sender, RoutedEventArgs e)
		{
			FireGraph();
		}

		/// <summary>
		/// Resets the Petri Net graph
		/// </summary>
		/// <param name="sender">Not used</param>
		/// <param name="e">Not used</param>
		private void resetButton_Click(object sender, RoutedEventArgs e)
		{
			ResetGraph();
		}

		#endregion

		#region menu listeners

		/// <summary>
		/// Listens for whent he save menu item is clicked and called Save
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">Not Used</param>
		private void saveMenuItem_Click(object sender, RoutedEventArgs e)
		{
			Save();
		}

		/// <summary>
		/// Listens for when the save as menu item is clicked and calls SaveAs
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">Not Used</param>
		private void saveAsMenuItem_Click(object sender, RoutedEventArgs e)
		{
			SaveAs();
		}

		/// <summary>
		/// Listens for when the load menu item is clicked and calls Load
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">Not Used</param>
		private void loadMenuItem_Click(object sender, RoutedEventArgs e)
		{
			Load();
		}

		/// <summary>
		/// Listens for when the exit menu item is clicked
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">Not Used</param>
		private void exitMenuItem_Click(object sender, RoutedEventArgs e)
		{
			Close();
		}

		/// <summary>
		/// Listens for when the export as png menu item is clicked
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">Not Used</param>
		private void exportMenuItem_Click(object sender, RoutedEventArgs e)
		{
			ExportGraphAsImage();
		}

		/// <summary>
		/// Listens for when the print menut item is clicked
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">Not Used</param>
		private void printMenuItem_Click(object sender, RoutedEventArgs e)
		{
			PrintGraph();
		}

		#endregion

		#region keyboard shorcuts

		/// <summary>
		/// Listens for keyboard shortcuts 
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">Determing which key was pressed and what modifiers
		/// key are active</param>
		private void Window_PreviewKeyUp(object sender, KeyEventArgs e)
		{
			//The save shortcut
			if (e.KeyboardDevice.Modifiers == ModifierKeys.Control && 
				e.Key == Key.S )
			{
				e.Handled = true;
				Save();
			}
			///The SaveAs shortcut
			else if (e.KeyboardDevice.Modifiers == (ModifierKeys.Control | ModifierKeys.Shift) &&
				e.Key == Key.S)
			{
				e.Handled = true;
				SaveAs();
			}
			//The load shortcut
			else if (e.KeyboardDevice.Modifiers == ModifierKeys.Control &&
				e.Key == Key.L)
			{
				e.Handled = true;
				Load();
			}
			//The export as png shortcut
			else if (e.KeyboardDevice.Modifiers == ModifierKeys.Control &&
				e.Key == Key.E)
			{
				e.Handled = true;
				ExportGraphAsImage();
			}
			//The print shortcut
			else if (e.KeyboardDevice.Modifiers == ModifierKeys.Control &&
				e.Key == Key.P)
			{
				e.Handled = true;
				PrintGraph();
			}
			//Modify mode shortcuts
			//Select Place Add
			else if (!graph.IsRunning &&
				e.KeyboardDevice.Modifiers == ModifierKeys.Control &&
				e.Key == Key.A)
			{
				e.Handled = true;
				placeRadioButton.IsChecked = true;
			}
			//Select Transition Add
			else if (!graph.IsRunning &&
				e.KeyboardDevice.Modifiers == ModifierKeys.Control &&
				e.Key == Key.T)
			{
				e.Handled = true;
				transitionRadioButton.IsChecked = true;
			}
			//Select Arc Add
			else if (!graph.IsRunning &&
				e.KeyboardDevice.Modifiers == ModifierKeys.Control &&
				e.Key == Key.C)
			{
				e.Handled = true;
				arcRadioButton.IsChecked = true;
			}
			//Select Cursor
			else if (!graph.IsRunning &&
				e.KeyboardDevice.Modifiers == ModifierKeys.Control &&
				e.Key == Key.U)
			{
				e.Handled = true;
				cursorRadioButton.IsChecked = true;
			}
			//Start running graph
			else if (!graph.IsRunning &&
				e.KeyboardDevice.Modifiers == ModifierKeys.Control &&
				e.Key == Key.R)
			{
				e.Handled = true;
				StartGraph();
			}
			//Running graph shortcuts
			//Manual Fire
			else if (graph.IsRunning &&
				e.KeyboardDevice.Modifiers == ModifierKeys.Control &&
				e.Key == Key.F)
			{
				e.Handled = true;
				if (graph.CanFire)
					FireGraph();
			}
			//Auto Fire
			else if (graph.IsRunning &&
				e.KeyboardDevice.Modifiers == ModifierKeys.Control &&
				e.Key == Key.A)
			{
				e.Handled = true;
				if (graph.CanFire)
					autoFireCheckBox.IsChecked = !autoFireCheckBox.IsChecked;
			}
			//Reset
			else if (graph.IsRunning &&
				e.KeyboardDevice.Modifiers == ModifierKeys.Control &&
				e.Key == Key.R)
			{
				e.Handled = true;
				ResetGraph();
			}
			//Modify
			else if (graph.IsRunning &&
				e.KeyboardDevice.Modifiers == ModifierKeys.Control &&
				e.Key == Key.M)
			{
				e.Handled = true;
				StopGraph();
			}
		}

		#endregion

		/// <summary>
		/// Listens for when the right mouse button is pressed down, this will change the 
		/// mode to cursor if the graph is running
		/// </summary>
		/// <param name="sender">Not used</param>
		/// <param name="e">Not used</param>
		private void graphCanvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
		{
			if (!graph.IsRunning)
				cursorRadioButton.IsChecked = true;
		}
		
	}
}
